#include "stdafx.h"
#include "jsys.h"

#include "directui.h"

#include <gtl/io/app_path.h>
#include <gtl/script/jscript.h>
#include <gtl/directui/visitor.h>
#include <gtl/thread/lambda.h>
#include <gtl/directui/script.h>
#include <gtl/crypto/sha.hpp>

#include <atlapp.h>
#include <atldlgs.h>

namespace gtl
{

namespace dui
{

jsys::jsys(const std::vector<gtl::tstr>& args)
{
}

v8::Handle<v8::Value> jsys::call(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::String::New("");

	gtl::tstr func_name(gtl::jscript::cstr(args, 0));
	if(func_name.empty())
		return v8::String::New("");

	std::vector<gtl::tstr> func_args;
	for(int i = 1; i < args.Length(); ++i)
	{
		if(args[i]->IsArray())
		{
			v8::Array* arr = v8::Array::Cast(*args[i]);
			uint32_t len = arr->Length();
			for(uint32_t i = 0; i < len; ++i)
			{
				v8::Local<v8::Value> obj = arr->Get(i);
				if(*obj == NULL || !obj->IsString())
					continue;

				v8::String::Utf8Value val(obj->ToString());
				func_args.push_back(gtl::tstr(gtl::jscript::cstr(val)));
			}
		}
		else if(args[i]->IsObject())
		{
			v8::Object* obj = v8::Object::Cast(*args[i]);
			if(obj == NULL)
				continue;

			v8::Local<v8::Array> prptynames = obj->GetPropertyNames();
			uint32_t len = prptynames->Length();
			for(uint32_t i = 0; i < len; ++i)
			{
				v8::Local<v8::Value> name = prptynames->Get(i);
				if(*name == NULL || !name->IsString())
					continue;

				v8::Local<v8::Value> value = obj->Get(name);
				if(*value == NULL || value->IsArray() || value->IsFunction() || value->IsObject() || value->IsNull() || value->IsUndefined())
					continue;

				v8::String::Utf8Value nm(name->ToString());
				v8::String::Utf8Value val(value->ToString());

				func_args.push_back(gtl::tstr(gtl::jscript::cstr(nm)));
				func_args.push_back(gtl::tstr(gtl::jscript::cstr(val)));
			}
		}
		else
		{
			func_args.push_back(gtl::tstr(gtl::jscript::cstr(args, i)));
		}
	}

	gtl::tstr result = m_directui->call(func_name, func_args);
	if(result.empty())
		return v8::String::New("");

#ifdef UNICODE
	return v8::String::New((const uint16_t*)result.c_str());
#else
	return v8::String::New(result.c_str());
#endif
}

v8::Handle<v8::Value> jsys::hash(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::String::New("");

	gtl::str str = gtl::jscript::cstr(args, 0);
	return v8::String::New(gtl::str(str.hash()));
}

v8::Handle<v8::Value> jsys::sha(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::String::New("");

	gtl::str str = gtl::jscript::cstr(args, 0);
	if(!str.empty())
	{
		gtl::sha sha;
		sha.feed(str.c_str());
		std::string hex = sha.hex();
		return v8::String::New(hex.c_str());
	}
	else if(args.Length() >= 2)
	{
		gtl::str path = gtl::jscript::cstr(args, 1);
		gtl::file file;
		if(!file.open(path, "rb"))
			return v8::String::New("");

		char hex[41] = {0};
		if(gtl::sha::get(file, hex, 40))
			return v8::String::New(hex);
	}

	return v8::String::New("");
}

v8::Handle<v8::Value> jsys::set_timer(const v8::Arguments& args)
{
	if(args.Length() < 2)
		return v8::String::New("invalid args");

	int elapse = atoi(gtl::jscript::cstr(args, 0));
	m_timer_func = gtl::jscript::cstr(args, 1);

	if(!m_timer_func.empty())
	{
		m_timer.start(elapse, &jsys::on_timer, this);
	}

	return v8::String::New("0");
}

v8::Handle<v8::Value> jsys::kill_timer(const v8::Arguments& args)
{
	m_timer.stop(0);

	return v8::Undefined();
}

v8::Handle<v8::Value> jsys::set_timeout(const v8::Arguments& args)
{
	return v8::Undefined();
}

void jsys::on_timer(int id)
{
	gtl::script* scrpt = m_directui->get_script();
	if(scrpt == NULL || m_timer_func.empty())
		return;

	scrpt->eval(m_timer_func);
}

v8::Handle<v8::Value> jsys::cursor_pos(const v8::Arguments& args)
{
	POINT pt_cursor = {0};
	GetCursorPos(&pt_cursor);

	v8::Local<v8::Object> pt = v8::Object::New();
	pt->Set(v8::String::New("x"), v8::String::New(gtl::str(pt_cursor.x)));
	pt->Set(v8::String::New("y"), v8::String::New(gtl::str(pt_cursor.y)));

	return pt;
}

v8::Handle<v8::Value> jsys::show_cursor(const v8::Arguments& args)
{
	if(args.Length() != 1)
		return v8::Undefined();

	bool value = gtl::jscript::cstr(v8::String::Utf8Value(args[0])).cast<bool>();
	ShowCursor(value);

	return v8::Undefined();
}

v8::Handle<v8::Value> jsys::path_exist(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::False();

	gtl::tstr path(gtl::jscript::cstr(args, 0));
	if(PathFileExists(path))
		return v8::True();

	return v8::False();
}

v8::Handle<v8::Value> jsys::get_current_path(const v8::Arguments& args)
{
	gtl::tstr path;
	GetCurrentDirectory(1024, path.resize(1024));

#ifdef UNICODE
	return v8::String::New((const uint16_t*)path.c_str());
#else
	return v8::String::New(path.c_str());
#endif
}

v8::Handle<v8::Value> jsys::set_current_path(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::False();

	bool result = false;
	gtl::tstr path(gtl::jscript::cstr(args, 0));
	if(!path.empty())
		result = SetCurrentDirectory(path) ? true : false;

	return result ? v8::True() : v8::False();
}

v8::Handle<v8::Value> jsys::shell_execute(const v8::Arguments& args)
{
	if(args.Length() < 1)
		return v8::Undefined();

	gtl::tstr param;
	gtl::tstr path(gtl::jscript::cstr(args, 0));
	if(args.Length() > 1)
		param = gtl::tstr(gtl::jscript::cstr(args, 1));

	gtl::str callback;
	if(args.Length() > 2)
		callback = gtl::str(gtl::jscript::cstr(args, 2));

	SHELLEXECUTEINFO sh_exec = {0};
	sh_exec.cbSize = sizeof(SHELLEXECUTEINFO);
	sh_exec.fMask = SEE_MASK_NOCLOSEPROCESS;
	sh_exec.lpVerb = _T("open");
	sh_exec.lpFile = path.c_str();
	sh_exec.lpParameters = param.c_str();
	sh_exec.nShow = SW_SHOW;
	ShellExecuteEx(&sh_exec);

	if(callback == NULL)
	{
		CloseHandle(sh_exec.hProcess);
	}
	else
	{
		shell_exec_callback_info* callback_info = new shell_exec_callback_info;
		callback_info->self = this;
		callback_info->process = sh_exec.hProcess;
		callback_info->callback = callback;

		CreateThread(NULL, NULL, gtl::lambda<DWORD (__stdcall*)(LPVOID)>([](LPVOID lParam) -> DWORD{ 
			shell_exec_callback_info* callback_info = (shell_exec_callback_info*)lParam;
			HANDLE hProcess = callback_info->process;
			WaitForSingleObject(hProcess, INFINITE);

			DWORD exit_code = -1;
			GetExitCodeProcess(hProcess, &exit_code);
			CloseHandle(hProcess);

			callback_info->self->m_callback.call(callback_info->callback, exit_code, [=](gtl::str clback, int code) {
				gtl::str jscall = clback + "(" + gtl::str(code) + ")";
				gtl::dui::script().eval(jscall);

				delete callback_info;
			});

			return 0; 
		}), callback_info, NULL, NULL);
	}
	
	return v8::Undefined();
}

v8::Handle<v8::Value> jsys::clipboard(const v8::Arguments& args)
{
	if(!::OpenClipboard(NULL))
		return v8::String::New("");

	gtl::tstr text(_T(""));

	do 
	{
		if(args.Length() == 0)
		{
			if(!IsClipboardFormatAvailable(CF_UNICODETEXT))
				break;

			HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT);
			if(hMem == NULL)
				break;

			text = (gtl::tchar*)GlobalLock(hMem);
			GlobalUnlock(hMem);
		}
		else
		{
			gtl::tstr data(gtl::jscript::cstr(args, 0));

			::EmptyClipboard();

			HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (data.length() + 1) * sizeof(gtl::tchar));
			if(hglbCopy == NULL)
				break;

			gtl::tchar* lpszCopy = (gtl::tchar*)GlobalLock(hglbCopy);
			memcpy(lpszCopy, data, data.length() * sizeof(gtl::tchar));
			lpszCopy[data.length()] = 0;
			GlobalUnlock(lpszCopy);

			SetClipboardData(CF_UNICODETEXT, hglbCopy);
		}
	} while (false);
	::CloseClipboard();

#ifdef UNICODE
	return v8::String::New((const uint16_t*)text.c_str());
#else
	return v8::String::New(text.c_str());
#endif
}

v8::Handle<v8::Value> jsys::dialog_open(const v8::Arguments& args)
{
	gtl::tstr filters = _T("All Files(*.*)|*.*||");
	if(args.Length() > 0)
	{
		gtl::tstr fltrs(gtl::jscript::cstr(args, 0));
		if(!fltrs.icmp(_T("undefined")))
			filters = fltrs;
	}

	filters.replace('|', '\0');
	CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filters);
	if(dlg.DoModal() != IDOK)
		return v8::String::New("");

#ifdef UNICODE
	return v8::String::New((const uint16_t*)dlg.m_szFileName);
#else
	return v8::String::New(dlg.m_szFileName);
#endif
}

v8::Handle<v8::Value> jsys::dialog_save(const v8::Arguments& args)
{
	gtl::tstr filters = _T("All Files(*.*)|*.*||");
	if(args.Length() > 0)
	{
		gtl::tstr fltrs(gtl::jscript::cstr(args, 0));
		if(!fltrs.icmp(_T("undefined")))
			filters = fltrs;
	}

	filters.replace('|', '\0');
	CFileDialog dlg(FALSE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filters);
	if(dlg.DoModal() != IDOK)
		return v8::String::New("");

#ifdef UNICODE
	return v8::String::New((const uint16_t*)dlg.m_szFileName);
#else
	return v8::String::New(dlg.m_szFileName);
#endif
}

gtl::str jsys::sys_path()
{
	struct path_info
	{
		const char* name;
		int index;
	} paths[] = {
		"app", 					0x00,
		"temp",					0x01,
		"programs",				0x02,
		"my_documents",			0x05,
		"user_favorites",		0x06,
		"startup",				0x07,
		"user_recent", 			0x08,
		"user_sendto", 			0x09,
		"user_start_menu",		0x0b,
		"local_my_documents",	0x0c,
		"my_music" ,			0x0d,
		"my_videos", 			0x0e,
		"user_desktop",			0x10,
		"all_users_start_menu",	0x16,
		"all_users_programs",	0x17,
		"all_users_startup",	0x18,
		"user_app_data",		0x1a,
		"user_local_app_data",	0x1c,
		"all_users_app_data",	0x23,
		"windows",				0x24,
		"system",				0x25,
		"program_files",		0x26,
		"my_pictures",			0x27,
		"systemx86",			0x29,
		"program_filesx86",		0x2a,
		"program_files_common",	0x2b,
		"all_users_documents",	0x2e,
		"all_users_music",		0x35,
		"all_users_pictures",	0x36,
		"all_users_video",		0x37,
	};

	auto get_path = [](path_info& info) -> gtl::str
	{
		gtl::tstr path;
		if(info.index == jsys::app_path)
			path = gtl::io::get_app_path<gtl::tstr>(NULL, false);
		else if(info.index == jsys::temp_path)
			GetTempPath(512, path.resize(512));
		else
			SHGetSpecialFolderPath(NULL, path.resize(512), info.index, FALSE);

		path.replace(_T("\\"), _T("\\\\"));
		return gtl::str("sys.path.") + info.name + "=\"" + gtl::str(path) + "\";";
	};

	gtl::str path = "sys.path = {};";
	for(int i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i)
		path += get_path(paths[i]);

	return path;
}

gtl::str jsys::os_version()
{
	OSVERSIONINFO osver = {0};
	osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if(!GetVersionEx(&osver))
		return "sys.os = { \"name\": \"unknow\", \"version\": 0 }";

	gtl::str name;
	if(osver.dwMajorVersion == 6 && osver.dwMinorVersion == 1)
		name = "win7";
	else if(osver.dwMajorVersion == 6 && osver.dwMinorVersion == 0)
		name = "vista";
	else if(osver.dwMajorVersion == 5 && osver.dwMinorVersion == 2)
		name = "svr2003";
	else if(osver.dwMajorVersion == 5 && osver.dwMinorVersion == 1)
		name = "winxp";
	else if(osver.dwMajorVersion == 5 && osver.dwMinorVersion == 0)
		name = "win2k";
	else if(osver.dwMajorVersion <= 4)
		name = "winnt";

	return "sys.os = { \"name\": \"" + name + "\", \"version\": " + gtl::str(osver.dwMajorVersion) + "." + gtl::str(osver.dwMinorVersion) + " }";
}

} // end of namespace dui

} // end of namespace gtl
